#include <mutex>
#include <string>
#include <time.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <memory>
using namespace std;

namespace LogModule
{
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNNING,
        ERROR,
        FATAL
    };

    string LogLevel2String(LogLevel level)
    {
        switch(level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNNING:
            return "WARNNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        }

        return "UNKNOWN";
    }

    string GetCurrTime()
    {
        time_t t= time(nullptr);
        struct tm time;
        //localtime(&t); 该函数只有一个参数，以返回值的形式返回struct tm*，
        //其返回的struct tm是一个静态变量，每次调用localtime都会覆盖该静态变量的值
        //所以多线程使用localtime是有并发读写问题的
        
        localtime_r(&t, &time);//localtime_r是可重入函数，因为需要你自己传入struct tm，
        //这样就不会多线程共享资源了

        char buffer[80];
        snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d",\
                time.tm_hour, time.tm_min, time.tm_sec);

        return buffer;
    }

    class LogStrategy
    {
    public:
        virtual ~LogStrategy()= default;
        virtual void SyncLog(const string& message)= 0;//同步日志到控制台或文件
    };

    class ConSoleLogStrategy : public LogStrategy
    {
    public:
        void SyncLog(const string& message) override
        {
            lock_guard<mutex> lg(_mutex);
            cerr<< message<< endl;
        }

        ~ConSoleLogStrategy()
        {}
    private:
        mutex _mutex;//ConsoleLogStrategy对象只会有一份作为多线程的共享资源，
        //如果某个线程要使用同步日志方法，那就需要先获取锁
    };

    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy()
        {}

        void SyncLog(const string& message) override
        {
            lock_guard<mutex> lg(_mutex);

            string log= _path+ _filename;
            ofstream out(log.c_str(), ios::app);
            out<<message<<endl;
            out.close();
        }

        ~FileLogStrategy()
        {}

    private:
        string _path= "./";
        string _filename= "log.txt";

        mutex _mutex;//日志文件就是共享资源，需要加锁保护
    };


    class Logger;


    class LogMessage
    {
    public:
        LogMessage(LogLevel level, string file, int line, Logger& lg)
        :_level(LogLevel2String(level))
        ,_time(GetCurrTime())
        ,_file(move(file))
        ,_line(line)
        ,_lg(lg)
        {
            stringstream ss;
            ss<<"["<<_time<<"] "
                <<"["<<_level<<"] "
                <<"["<<_file<<":"<<_line<<"] ";

            _info= ss.str();
        }

        template<class T>
        LogMessage& operator<<(const T& t) 
        {
            stringstream ss;
            ss<<t;

            _info+= ss.str();
            return *this;
        }

        ~LogMessage();
       
    private:
        string _time;
        string _level;
        string _file;
        int _line;
        
        string _info;

        Logger& _lg;
    };


    class Logger
    {
        friend class LogMessage;
    public:
        void UseConsoleLogStrategy()
        {
            _strategy= make_unique<ConSoleLogStrategy>();
        }

        void UseFileLogStrategy()
        {
            _strategy= make_unique<FileLogStrategy>();
        }

        Logger()
        {   
            UseConsoleLogStrategy();
        }

        LogMessage operator()(LogLevel level)
        {
            return LogMessage(level,__FILE__, __LINE__,*this);
        }



    private:
        unique_ptr<LogStrategy> _strategy;
    };


    LogMessage::~LogMessage()
    {
        //允许内部类访问外部类的private成员，因为内部类是外部类天然的友元类
        //但内部类成员函数没有外部类的this指针，所以必须先获取到外部类对象，才能接着访问其private成员
        if(_lg._strategy )
            _lg._strategy->SyncLog(_info);
    }


Logger lg;

#define LOG(level) lg(level) 

#define ENABLE_CONSOLE_LOG_STRATEGY() lg.UseConsoleLogStrategy()
#define ENABLE_FILE_LOG_STRATEGY() lg.UseFileLogStrategy()
}